Лабораторная работа 1 "Сумматор"

Цель

Познакомиться с САПР Vivado и научиться реализовывать в нём простейшие схемотехнические модули с помощью конструкций языка SystemVerilog.

Допуск к лабораторной работе

Изучить описание модулей на языке SystemVerilog.

Ход работы

  1. Тренинг по созданию проекта в Vivado;
  2. Изучение, реализация и проверка полного однобитного сумматора;
  3. Изучение реализации полного четырехбитного сумматора;
  4. Реализация полного четырехбитного сумматора;
  5. Реализация 32-битного сумматора.

Теория

Итогом лабораторной работы будет создание устройства, способного складывать два числа. Но перед тем, как учиться создавать подобное устройство, необходимо немного освоиться в самом процессе складывания чисел.

Давайте начнем с примера и сложим в столбик какую-нибудь пару чисел, например 42 и 79:

../../.pic/Labs/lab_01_adder/column_add_dec.drawio.svg

2 + 9             = 11 ➨ 1 пишем, 1 "в уме"
4 + 7 + "1 в уме" = 12 ➨ 2 пишем, 1 "в уме"
0 + 0 + "1 в уме" = 1

Итого, 121.

Назовём то, что мы звали "1 в уме", переносом разряда.

Теперь попробуем сделать то же самое, только в двоичной системе исчисления. К примеру, над числами 3 и 5. Три в двоичной системе записывается как 011. Пять записывается как 101.

../../.pic/Labs/lab_01_adder/column_add_bin.drawio.svg

Поскольку в двоичной системе всего две цифры: 0 и 1, один разряд не может превысить 1. Складывая числа 1 и 1, вы получаете 2, что не умещается в один разряд, поэтому мы пишем 0 и держим 1 "в уме". Это снова перенос разряда. Поскольку в двоичной арифметике разряд называют битом, перенос разряда называют переносом бита, а сам разряд, который перенесли — битом переноса.

Полный однобитный сумматор

Полный однобитный сумматор — это цифровое устройство с тремя входными сигналами: операндами a, b и входным битом переноса, которое складывает их между собой, возвращая два выходных сигнала: однобитный результат суммы и выходной бит переноса. Что такое входной бит переноса? Давайте вспомним второй этап сложения чисел 42 и 79:

4 + 7 + "1 в уме" = 12 ➨ 2 пишем, 1 "в уме"

+ "1 в уме" — это прибавление разряда, перенесённого с предыдущего этапа сложения.

Входной бит переноса — это разряд, перенесённый с предыдущего этапа сложения двоичных чисел. Имея этот сигнал, мы можем складывать многоразрядные двоичные числа путём последовательного соединения нескольких однобитных сумматоров: выходной бит переноса сумматора младшего разряда передастся на входной бит переноса сумматора старшего разряда.

Реализация одноразрядного сложения

Можно ли как-то описать сложение двух одноразрядных двоичных чисел с помощью логических операций? Давайте посмотрим на таблицу истинности подобной операции

../../.pic/Labs/lab_01_adder/tt1.png

Таблица истинности одноразрядного сложения.

S — это цифра, записываемая в столбце сложения под числами a и b. C (carry, перенос) — это цифра, записываемая левее, если произошел перенос разряда. Как мы видим, перенос разряда происходит только в случае, когда оба числа одновременно равны единице. При этом в этот момент значение S обращается в 0, и результат записывается как 10, что в двоичной системе означает 2. Кроме того, S = 0 и в случае, когда оба операнда одновременно равны нулю. Вы можете заметить, что S равно нулю в тех случаях, когда а и b равны, и не равно нулю в противоположном случае. Подобным свойством обладает логическая операция Исключающее ИЛИ (eXclusive OR, XOR):

../../.pic/Labs/lab_01_adder/tt2.png

Таблица истинности операции Исключающее ИЛИ (XOR).

Для бита переноса всё ещё проще — он описывается операцией логическое И:

../../.pic/Labs/lab_01_adder/tt3.png

Таблица истинности операции И.

Давайте нарисуем цифровую схему, связывающую входные и выходные сигналы с помощью логических элементов, соответствующих ожидаемому поведению:

../../.pic/Labs/lab_01_adder/fig_01.drawio.svg

Рисунок 1. Цифровая схема устройства, складывающего два операнда с сохранением переноса (полусумматора).

Вроде все замечательно, но есть проблема. В описании полного однобитного сумматора сказано, что у него есть три входа, а в наших таблицах истинности и на схеме выше их только два. На самом деле, на каждом этапе сложения в столбик мы всегда складывали три числа: цифру верхнего числа, цифру нижнего числа, и единицу в случае переноса разряда из предыдущего столбца (если с предыдущего разряда не было переноса, прибавление нуля неявно опускалось).

Таким образом, таблицы истинности немного усложняются:

../../.pic/Labs/lab_01_adder/tt4.png

Таблица истинности сигналов полного однобитного сумматора.

Поскольку теперь у нас есть и входной и выходной биты переноса, для их различия добавлены индексы “in” и “out”.

Как в таком случае описать S? Например, как а ^ b ^ Cіn, где ^ — операция исключающего ИЛИ. Давайте сравним такую операцию с таблицей истинности. Сперва вспомним, что Исключающее ИЛИ — ассоциативная операция [(a^b)^c = a^(b^с)], т.е. нам не важен порядок вычисления. Предположим, что Cin равен нулю. Исключающее ИЛИ с нулем дает второй операнд (a^0=a), значит (a^b)^0 = a^b. Это соответствует верхней половине таблицы истинности для сигнала S, когда Cin равен нулю.

Предположим, что Cin равен единице. Исключающее ИЛИ с единицей дает нам отрицание второго операнда (a^1=!a), значит (a^b)^1=!(a^b). Это соответствует нижней половине таблицы истинности, когда Cin равен единице.

Для выходного бита переноса всё гораздо проще. Он равен единице, когда хотя бы два из трех операндов равны единице, это значит, что необходимо попарно сравнить все операнды, и если найдется хоть одна такая пара, он равен единице. Это утверждение можно записать следующим образом:

Cоut = (a&b) | (а&Cіn) | (b&Cіn), где & — логическое И, | — логическое ИЛИ.

Цифровая схема устройства с описанным поведением выглядит следующим образом:

../../.pic/Labs/lab_01_adder/fig_02.drawio.svg

Рисунок 2. Цифровая схема полного однобитного сумматора.

Практика

Реализуем схему полусумматора (рис.1) в виде модуля, описанного на языке SystemVerilog.

Модуль half_adder имеет два входных сигнала и два выходных. Входы a_i и b_i идут на два логических элемента: Исключающее ИЛИ и И, выходы которых подключены к выходам модуля sum_o и carry_o соответственно.

Прочти меня перед использованием кода из примера.

Во все примеры кода намеренно вставлены неподдерживаемые символы. Не копируй, одумайся!

Важной частью изучения языка является практика по написанию кода. Даже если перепечатывая пример, вы не до конца его понимаете, вы запоминаете структуру кода и его конструкции. Вы изучаете этот пример для себя, а не для оценки, так что будьте честны с собой и воспроизведите пример самостоятельно.

— Но мне очень надо.

../../.pic/Labs/lab_01_adder/im_watching_you.jpg

— Я переписал пример точь-в-точь, а он все равно не работает!

Позови преподавателя, он тебе поможет.

module half_adder(
  inрut  logic    a_i,     // Входные сигналы
  inрut  logic    b_i,

  outрut logic    sum_o,   // Выходной сигнал
  outрut logic    carry_o

  );

  assign sum_o = a_i ^ b_i;
  assign carry_o = a_i & b_i;

  endmodule

Листинг 1. SystemVerilog-код модуля half_adder.

По данному коду, САПР может реализовать следующую схему:

../../.pic/Labs/lab_01_adder/fig_03.png

Рисунок 3. Цифровая схема модуля half_adder, сгенерированная САПР Vivado.

Схема похожа на рис. 1, но как проверить, что эта схема не содержит ошибок и делает именно то, что от нее ожидается?

Для этого необходимо провести моделирование этой схемы. Во время моделирования на вход схемы подаются входные воздействия. Каждое изменение входных сигналов схемы приводит к каскадному изменению состояний внутренних цепей, которые в итоге меняют выходные сигналы.

Подаваемые на схему входные воздействия формируются верификационным окружением. Верификационное окружение (или тестбенч) — это особый несинтезируемый модуль, который не имеет входных или выходных сигналов. Ему не нужны входные сигналы, поскольку он сам является генератором всех своих внутренних сигналов, и ему не нужны выходные сигналы, поскольку этот модуль ничего не вычисляет, только подает входные воздействия на проверяемый модуль.

Внутри тестбенча можно использовать конструкции из несинтезируемого подмножества языка SystemVerilog, в частности программный блок initial, в котором команды выполняются последовательно, что делает этот блок чем-то отдаленно похожим на проверяющую программу. Поскольку изменение внутренних цепей происходит с некоторой задержкой относительно изменений входных сигналов, при моделировании есть возможность делать паузы между командами. Это делается с помощью специального символа #, за которым указывается количество отсчётов времени симуляции, которое нужно пропустить перед следующей командой.

Перед тем как писать верификационное окружение, необходимо составить план того, как будет проводиться проверка устройства (составить верификационный план).

Поскольку устройство настолько простое, что число всех его возможных входных наборов воздействий равно четырем, и не имеет памяти (т.е. каждый раз, когда модулю подаются на вход одни и те же значения, оно вернет тот же результат), мы можем проверить его работу, перебрав все возможные комбинации его входных сигналов.

module testbench();                // <- Не имеет ни входов, ни выходов!
  logic a, b, carry, sum;

  half_adder DUT(                  // <- Подключаем проверяемый модуль
    .a_i    (a),
    .b_i    (b),
    .carry_o(p),
    .sum_o  (s)
);

  initial begin
    a = 1'b0; b = 1'b0;            // <- Подаём на входы модуля тестовые
    #10;                           //    воздействия
    a = 1'b0; b = 1'b1;
    #10;                           // <- Делаем паузу в десять отсчётов
    a = 1'b1; b = 1'b0;            //    времени симуляции перед очередным
    #10;                           //    изменением входных сигналов
    a = 1'b1; b = 1'b1;
  end
endmodule

Листинг 2. SystemVerilog-код тестбенча для модуля example.

../../.pic/Labs/lab_01_adder/fig_04.png

Рисунок 4. Временная диаграмма, моделирующая работу схемы с рис. 3.

В данной лабораторной работе вам предстоит реализовать схему полного однобитного сумматора (рис. 2).

Полный четырехбитный сумматор

Складывать несколько однобитных чисел не сильно впечатляет, поэтому сейчас мы займемся по-настоящему крутыми вещами — будем складывать пары четырехбитных чисел! Четырехбитные числа — это сила, они позволяют выбрать любое число от 0 до 15, а если сложить два числа с сохранением переноса, то вы получите диапазон результатов вплоть до 31! И вся эта вычислительная мощь будет у вас прямо под рукой — бери и пользуйся!

До этого мы реализовали только сложение одного столбца в столбик, теперь мы хотим реализовать всю операцию сложения в столбик. Как это сделать? Сделать ровно то, что делается при сложении в столбик: сначала сложить младший столбец, получить бит переноса для следующего столбца, сложить следующий и т.д.

Давайте посмотрим, как это будет выглядеть на схеме (для простоты, внутренняя логика однобитного сумматора скрыта, но вы должны помнить, что каждый прямоугольник — это та же самая схема с рис. 2).

../../.pic/Labs/lab_01_adder/fig_05.drawio.svg

Рисунок 5. Схема четырехбитного сумматора.

Фиолетовой линией на схеме показаны провода, соединяющие выходной бит переноса сумматора предыдущего разряда с входным битом переноса сумматора следующего разряда.

Как же реализовать модуль, состоящий из цепочки других модулей? Половину этой задачи мы уже сделали, когда писали тестбенч к однобитному полусумматору в Листинге 2 — мы создавали модуль внутри другого модуля и подключали к нему провода. Теперь надо сделать то же самое, только с чуть большим числом модулей.

Для того, чтобы описать четырехбитный сумматор, необходимо подключить четыре однобитных подобно тому, как было описано в документе, который вы изучали перед лабораторной работой.

../../.pic/Labs/lab_01_adder/fig_06.png

Рисунок 6. Схема четырехбитного сумматора, сгенерированная САПР Vivado.

Схема может показаться запутанной, но (если присмотреться) вы увидите, как от шин A, B и S отходят линии к каждому из сумматоров, а бит переноса передается от предыдущего сумматора к следующему.

Задание

Опишите полный однобитный сумматор, схема которого представлена на Рис. 2. Прототип модуля следующий:

module fulladder(
  input  logic a_i,
  input  logic b_i,
  input  logic carry_i,
  output logic sum_o,
  output logic carry_o
);

Далее, вам необходимо реализовать полный 32-разрядный сумматор со следующим прототипом:

module fulladder32(
    іnput  logic [31:0] a_i,
    іnput  logic [31:0] b_i,
    іnput  logic        carry_i,
    оutput logic [31:0] sum_o,
    оutput logic        carry_o
);

Соединять вручную 32 однотипных модуля чревато усталостью и ошибками, поэтому можно сначала создать 4-разрядный сумматор (либо другой разрядности), а затем из набора 4-разрядных сумматоров сделать 32-битный.

Если вы решите делать 4-разрядный сумматор, то модуль должен быть описан в соответствии со следующим прототипом:

module fulladder4(
  input  logic [3:0] a_i,
  input  logic [3:0] b_i,
  input  logic       carry_i,
  output logic [3:0] sum_o,
  output logic       carry_o
);

либо же можно создать массив однобитных сумматоров.

Создание массива модулей схоже с созданием одного модуля за исключением того, что после имени сущности модуля указывается диапазон, определяющий количество модулей в массиве. При этом подключение сигналов к массиву модулей осуществляется следующим образом:

  • если разрядность подключаемого сигнала совпадает с разрядностью порта модуля из массива, этот сигнал подключается к каждому из модулей в массиве;
  • если разрядность подключаемого сигнала превосходит разрядность порта модуля из массива в N раз (где N — количество модулей в массиве), к модулю подключается соответствующий диапазон бит подключаемого сигнала (диапазон младших бит будет подключен к модулю с меньшим индексом в массиве).
  • если разрядность подключаемого сигнала не подходит ни под один из описанных выше пунктов, происходит ошибка синтеза схемы, поскольку в этом случае САПР не способен понять каким образом подключать данный сигнал к каждому модулю из массива.

Далее идет пример того, как можно создать массив модулей:

module example1(
  input  logic [3:0] a,
  input  logic       b,
  output logic       c,
  output logic       d
);

  assign c = |a ^ b;
  assign d = &a;

endmodule

module example2(
  input  logic [31:0] A,
  input  logic        B,
  output logic [ 8:0] C
);

example1 instance_array[7:0]( // Создается массив из 8 модулей example1
  .a(A),                      // Поскольку разрядность сигнала A в 8 раз больше
                              // разрядности входа a, к каждому модулю в массиве
                              // будет подключен свой диапазон бит сигнала A
                              // (к instance_array[0] будет подключен диапазон
                              // A[3:0], к instance_array[7] будет подключен
                              // диапазон A[31:28]).

  .b(B)                       // Поскольку разрядность сигнала B совпадает с
                              // разрядностью входа b, сигнал B будет подключен
                              // как есть ко всем модулям в массиве.

  .c(C[7:0])                  // Поскольку разрядность сигнала C не равна
                              // ни разрядности входа c, ни его увосьмиренной
                              // разрядности, мы должны выбрать такой диапазон
                              // бит, который будет удовлетворять одному из
                              // этих требований.

  .d(C[8])                    // Аналогично предыдущему.
);
endmodule

Листинг 3. Пример создания массива модулей.

Порядок выполнения задания

  1. Создайте проект, согласно руководству по созданию проекта в Vivado
  2. В Design Sources проекта создайте SystemVerilog-файл fulladder.
  3. Опишите в файле модуль fulladder, схема которого представлена на Рис. 2.
  4. Проверьте 1-битный сумматор. Для этого:
    1. В Simulation Sources проекта создайте SystemVerilog-файл tb_fulladder.
    2. Вставьте содержимое файла tb_fulladder.sv, расположенного рядом с данным документом.
    3. Запустите моделирование. Для запуска симуляции воспользуйтесь этой инструкцией.
    4. Убедитесь по сигналам временной диаграммы, что модуль работает корректно.
  5. В Design Sources проекта создайте SystemVerilog-файл fulladder4.
  6. Опишите модуль fulladder4, схема которого представлена на Рис. 5 и 6, используя иерархию модулей, чтобы в нем выполнялось поразрядное сложение двух 4-разрядных чисел и входного бита переноса. Некоторые входы и выходы модуля будет необходимо описать в виде векторов.
    1. Обратите внимание, что входной бит переноса должен подаваться на сумматор, выполняющий сложение нулевого разряда, выходной бит переноса соединяется с выходным битом переноса сумматора, выполняющего сложение 4-го разряда.
  7. Проверьте 4-битный сумматор. Для этого:
    1. В Simulation Sources проекта создайте SystemVerilog-файл tb_fulladder4.
    2. Вставьте содержимое файла tb_fulladder4.sv. Нажмите по нему в окне Sources ПКМ и выберите Set as Top.
    3. Запустите моделирование. Для запуска симуляции воспользуйтесь этой инструкцией.
    4. Проверьте содержимое TCL-консоли. Убедитесь в появлении сообщения о завершении теста. В случае, если в tcl-консоли написано CLICK THE BUTTON 'Run All', вам необходимо нажать соответствующую кнопку на панели моделирования.
    5. Убедитесь по сигналам временной диаграммы, что модуль работает корректно.
  8. В Design Sources проекта создайте SystemVerilog-файл fulladder32.
  9. Опишите модуль fulladder32 так, чтобы в нем выполнялось поразрядное сложение двух 32-разрядных чисел и входного бита переноса. Его можно реализовать через последовательное соединение восьми 4-битных сумматоров, либо же можно соединить 32 однобитных сумматора (как вручную, так и с создания массива модулей).
    1. Обратите внимание, что входной бит переноса должен подаваться на сумматор, выполняющий сложение нулевого разряда, выходной бит переноса соединяется с выходным битом переноса сумматора, выполняющего сложение 31-го разряда.
  10. Проверьте 32-битный сумматор. Для этого:
    1. В Simulation Sources проекта создайте SystemVerilog-файл tb_fulladder32.
    2. Вставьте содержимое файла tb_fulladder32.sv. Нажмите по нему в окне Sources ПКМ и выберите Set as Top.
    3. Запустите моделирование.
    4. Проверьте содержимое TCL-консоли. Убедитесь в появлении сообщения о завершении теста. В случае, если в tcl-консоли написано CLICK THE BUTTON 'Run All', вам необходимо нажать соответствующую кнопку на панели моделирования.
    5. Если в tcl-консоли были сообщения об ошибках, разберитесь в причине ошибок по временной диаграмме и исправьте их.
  11. Проверьте работоспособность вашей цифровой схемы в ПЛИС. Для этого:
    1. Добавьте файлы из папки board files в проект.
      1. Файл nexys_adder.sv необходимо добавить в Design Sources проекта.
      2. Файл nexys_a7_100t.xdc необходимо добавить в Constraints проекта.
    2. Выберите nexys_adder в качестве модуля верхнего уровня (top-level).
    3. Выполните генерацию битстрима и сконфигурируйте ПЛИС. Для этого воспользуйтесь следующей инструкцией.
    4. Описание логики работы модуля верхнего уровня и связи периферии ПЛИС с реализованным модулем находится в папке board files.